package com.gph.saviorframework.os.authz.service.impl;

import com.gph.saviorframework.os.authz.dao.AccessTokenDao;
import com.gph.saviorframework.os.authz.dao.ClientDetailsDao;
import com.gph.saviorframework.os.authz.dao.OauthCodeDao;
import com.gph.saviorframework.os.authz.service.OauthService;
import com.gph.saviorframework.os.authz.service.impl.generator.AuthenticationIdGenerator;
import com.gph.saviorframework.os.model.oauth.AccessToken;
import com.gph.saviorframework.os.model.oauth.ClientDetails;
import com.gph.saviorframework.os.model.oauth.OauthCode;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.utils.OAuthUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.Set;

/**
 * Created by guopeihui on 2017/2/9.
 */
public class OauthServiceImpl implements OauthService {

    private static final Logger log = LoggerFactory.getLogger(OauthServiceImpl.class);

    @Autowired
    private AccessTokenDao accessTokenDao;

    @Autowired
    private ClientDetailsDao clientDetailsDao;

    @Autowired
    private OauthCodeDao oauthCodeDao;

    @Autowired
    private OAuthIssuer oAuthIssuer;

    @Autowired
    private AuthenticationIdGenerator authenticationIdGenerator;

    @Override
    public ClientDetails loadClientDetails(String clientId) {
        List<ClientDetails> clientDetailses=clientDetailsDao.findClientDetails(clientId);
        return clientDetailses.size()>0?clientDetailses.get(0):null;
    }

    @Override
    public OauthCode saveAuthorizationCode(String authCode, ClientDetails clientDetails,String currentUsername) {
        OauthCode oauthCode = new OauthCode()
                .code(authCode).username(currentUsername)
                .clientId(clientDetails.getClientId());

        oauthCodeDao.saveOauthCode(oauthCode);
        return oauthCode;
    }

    @Override
    public String retrieveAuthCode(ClientDetails clientDetails,String currentUsername) throws OAuthSystemException {
        final String clientId = clientDetails.getClientId();
        final String username = currentUsername;

        OauthCode oauthCode = oauthCodeDao.findOauthCodeByUsernameClientId(username, clientId);
        if (oauthCode != null) {
            //Always delete exist
            log.debug("OauthCode ({}) is existed, remove it and create a new one", oauthCode);
            oauthCodeDao.deleteOauthCode(oauthCode);
        }
        //create a new one
        oauthCode = createOauthCode(clientDetails,currentUsername);

        return oauthCode.code();
    }

    private OauthCode createOauthCode(ClientDetails clientDetails,String currentUsername) throws OAuthSystemException {
        OauthCode oauthCode;
        final String authCode = oAuthIssuer.authorizationCode();
        log.debug("Save authorizationCode '{}' of ClientDetails '{}'", authCode, clientDetails);
        oauthCode = this.saveAuthorizationCode(authCode, clientDetails, currentUsername);
        return oauthCode;
    }


    @Override
    public AccessToken retrieveAccessToken(ClientDetails clientDetails,
                                           Set<String> scopes,
                                           boolean includeRefreshToken,
                                           String currentUsername) throws OAuthSystemException {
        String scope = OAuthUtils.encodeScopes(scopes);
        final String clientId = clientDetails.getClientId();

        final String authenticationId = authenticationIdGenerator.generate(clientId, currentUsername, scope);

        AccessToken accessToken = accessTokenDao.findAccessToken(clientId, currentUsername, authenticationId);
        if (accessToken == null) {
            accessToken = createAndSaveAccessToken(clientDetails, includeRefreshToken, currentUsername, authenticationId);
            log.debug("Create a new AccessToken: {}", accessToken);
        }

        return accessToken;
    }

    @Override
    public AccessToken retrieveNewAccessToken(ClientDetails clientDetails, Set<String> scopes,String currentUsername) throws OAuthSystemException {
        String scope = OAuthUtils.encodeScopes(scopes);
        final String username = currentUsername;
        final String clientId = clientDetails.getClientId();

        final String authenticationId = authenticationIdGenerator.generate(clientId, username, scope);

        AccessToken accessToken = accessTokenDao.findAccessToken(clientId, username, authenticationId);
        if (accessToken != null) {
            log.debug("Delete existed AccessToken: {}", accessToken);
            accessTokenDao.deleteAccessToken(accessToken);
        }
        accessToken = createAndSaveAccessToken(clientDetails, false, username, authenticationId);
        log.debug("Create a new AccessToken: {}", accessToken);

        return accessToken;
    }

    private AccessToken createAndSaveAccessToken(ClientDetails clientDetails, boolean includeRefreshToken, String username, String authenticationId) throws OAuthSystemException {
        AccessToken accessToken = new AccessToken()
                .clientId(clientDetails.getClientId())
                .username(username)
                .tokenId(oAuthIssuer.accessToken())
                .authenticationId(authenticationId)
                .updateByClientDetails(clientDetails);

        if (includeRefreshToken) {
            accessToken.refreshToken(oAuthIssuer.refreshToken());
        }

        accessTokenDao.saveAccessToken(accessToken);
        log.debug("Save AccessToken: {}", accessToken);
        return accessToken;
    }

    @Override
    public OauthCode loadOauthCode(String code, ClientDetails clientDetails) {
        final String clientId = clientDetails.getClientId();
        return oauthCodeDao.findOauthCode(code, clientId);
    }

    @Override
    public boolean removeOauthCode(String code, ClientDetails clientDetails) {
        final OauthCode oauthCode = loadOauthCode(code, clientDetails);
        final int rows = oauthCodeDao.deleteOauthCode(oauthCode);
        return rows > 0;
    }

    @Override
    public AccessToken retrieveAuthorizationCodeAccessToken(ClientDetails clientDetails, String code) throws OAuthSystemException {
        final OauthCode oauthCode = loadOauthCode(code, clientDetails);
        final String username = oauthCode.username();
        final String clientId = clientDetails.getClientId();

        final String authenticationId = authenticationIdGenerator.generate(clientId, username, null);

        AccessToken accessToken = accessTokenDao.findAccessToken(clientId, username, authenticationId);
        if (accessToken != null) {
            log.debug("Delete existed AccessToken: {}", accessToken);
            accessTokenDao.deleteAccessToken(accessToken);
        }
        accessToken = createAndSaveAccessToken(clientDetails, clientDetails.supportRefreshToken(), username, authenticationId);
        log.debug("Create a new AccessToken: {}", accessToken);

        return accessToken;
    }

    @Override
    public AccessToken retrievePasswordAccessToken(ClientDetails clientDetails, Set<String> scopes, String username) throws OAuthSystemException {
        String scope = OAuthUtils.encodeScopes(scopes);
        final String clientId = clientDetails.getClientId();

        final String authenticationId = authenticationIdGenerator.generate(clientId, username, scope);
        AccessToken accessToken = accessTokenDao.findAccessToken(clientId, username, authenticationId);

        boolean needCreate = false;
        if (accessToken == null) {
            needCreate = true;
            log.debug("Not found AccessToken from repository, will create a new one, client_id: {}", clientId);
        }
        else if (accessToken.tokenExpired()) {
            log.debug("Delete expired AccessToken: {} and create a new one, client_id: {}", accessToken, clientId);
            accessTokenDao.deleteAccessToken(accessToken);
            needCreate = true;
        }
        else {
            log.debug("Use existed AccessToken: {}, client_id: {}", accessToken, clientId);
        }

        if (needCreate) {
            accessToken = createAndSaveAccessToken(clientDetails, clientDetails.supportRefreshToken(), username, authenticationId);
            log.debug("Create a new AccessToken: {}", accessToken);
        }

        return accessToken;
    }

    @Override
    public AccessToken retrieveClientCredentialsAccessToken(ClientDetails clientDetails, Set<String> scopes) throws OAuthSystemException {
        String scope = OAuthUtils.encodeScopes(scopes);
        final String clientId = clientDetails.getClientId();
        //username = clientId

        final String authenticationId = authenticationIdGenerator.generate(clientId, clientId, scope);
        AccessToken accessToken = accessTokenDao.findAccessToken(clientId, clientId, authenticationId);

        boolean needCreate = false;
        if (accessToken == null) {
            needCreate = true;
            log.debug("Not found AccessToken from repository, will create a new one, client_id: {}", clientId);
        }
        else if (accessToken.tokenExpired()) {
            log.debug("Delete expired AccessToken: {} and create a new one, client_id: {}", accessToken, clientId);
            accessTokenDao.deleteAccessToken(accessToken);
            needCreate = true;
        }
        else {
            log.debug("Use existed AccessToken: {}, client_id: {}", accessToken, clientId);
        }

        if (needCreate) {
            //Ignore refresh_token
            accessToken = createAndSaveAccessToken(clientDetails, false, clientId, authenticationId);
            log.debug("Create a new AccessToken: {}", accessToken);
        }

        return accessToken;
    }

    @Override
    public AccessToken loadAccessTokenByRefreshToken(String refreshToken, String clientId) {
        log.debug("Load ClientDetails by refreshToken: {} and clientId: {}", refreshToken, clientId);
        return accessTokenDao.findAccessTokenByRefreshToken(refreshToken, clientId);
    }

    @Override
    public AccessToken changeAccessTokenByRefreshToken(String refreshToken, String clientId) throws OAuthSystemException {
        final AccessToken oldToken = loadAccessTokenByRefreshToken(refreshToken, clientId);

        AccessToken newAccessToken = oldToken.cloneMe();
        log.debug("Create new AccessToken: {} from old AccessToken: {}", newAccessToken, oldToken);

        List<ClientDetails> clientDetailses = clientDetailsDao.findClientDetails(clientId);

        ClientDetails details = clientDetailses.size()>0?clientDetailses.get(0):null;
        newAccessToken.updateByClientDetails(details);

        final String authId = authenticationIdGenerator.generate(clientId, oldToken.username(), null);
        newAccessToken.authenticationId(authId)
                .tokenId(oAuthIssuer.accessToken())
                .refreshToken(oAuthIssuer.refreshToken());

        accessTokenDao.deleteAccessToken(oldToken);
        log.debug("Delete old AccessToken: {}", oldToken);

        accessTokenDao.saveAccessToken(newAccessToken);
        log.debug("Save new AccessToken: {}", newAccessToken);

        return newAccessToken;
    }

    @Override
    public boolean isExistedClientId(String clientId) {
        final ClientDetails clientDetails = loadClientDetails(clientId);
        return clientDetails != null;
    }
}
